#!/usr/bin/env python
import asyncio
import time
import datetime
from queue import Empty as QueueEmpty
from multiprocessing import Process, Queue, Event as MPEvent, Value
from multiprocessing.synchronize import Event as MPEventType
from multiprocessing.sharedctypes import Synchronized as MPValueType
from websockets.asyncio.server import serve, broadcast, ServerConnection as WSSConn
from contextvars import ContextVar
import tkinter as tk
from tkinter import scrolledtext
from tk_debounced_numeric_entry import DebouncedNumericEntry
from typing import Dict, List
from pynput import keyboard

from config_manager import ConfigManager

from utils import (
    get_connection_status_message,
    connections2str,
    get_client_address_str,
    queue_print,
    set_window_icon_from_base64,
    dict_to_hotkey_str,
    hotkey_str_to_dict,
    generate_random_hotkeys,
    open_project_homepage,
)
from utils2 import show_script_window


# 第一个进程：运行 Tkinter 界面
def run_tkinter_interface(
    stop_process_event: MPEventType,
    restart_ws_event: MPEventType,
    restart_pl_event: MPEventType,
    shared_ws_port: MPValueType,
    hotkey_config_queue: Queue,
    user_hotkey: Dict[str, str],
    qpq: Queue,
):
    def on_close():
        """关闭窗口时设置停止事件, 通知其他进程停止"""
        stop_process_event.set()
        if root:
            root.destroy()

    def watch_stop_event():
        """定期检查是否需要停止 Tkinter 窗口"""
        if stop_process_event.is_set() and root:
            root.destroy()
        elif root:
            root.after(300, watch_stop_event)  # 0.3秒检查一次是否需要停止

    def restart_websocket():
        # 添加冷却时间
        restart_websocket_button.config(state=tk.DISABLED)
        restart_websocket_button.after(
            500, lambda: restart_websocket_button.config(state=tk.NORMAL)
        )

        port = port_entry.get().strip()
        with shared_ws_port.get_lock():
            shared_ws_port.value = int(port)
        restart_ws_event.set()

    def restart_keyboard_listener():
        # 添加冷却时间
        restart_keyboard_listener_button.config(state=tk.DISABLED)
        restart_keyboard_listener_button.after(
            500, lambda: restart_keyboard_listener_button.config(state=tk.NORMAL)
        )

        # 将 hotkey_config_textbox 中的值放入 hotkey_config_queue
        hotkey_config_str = hotkey_config_textbox.get("1.0", tk.END).strip()
        hotkey_config_queue.put(hotkey_config_str)

        restart_pl_event.set()  # 设置事件, 通知 pynput 重启监听

    def check_hotkey_config_text_limit(event=None):
        """
        检查快捷键配置文本框的输入字符数是否超过限制, 并截断过长的文本
        """
        # 获取文本框内容（自动去除末尾换行符）
        content = hotkey_config_textbox.get("1.0", "end-1c")
        current_length = len(content)
        # 如果超过限制则截断
        limit = 300
        if current_length > limit:
            hotkey_config_textbox.delete("1.0 + " + str(limit) + " chars", "end")
            queue_print(qpq, f"最多输入 {limit} 个字符！")

    def set_textbox_with_random_hotkeys():
        s = generate_random_hotkeys()
        hotkey_config_textbox.delete("0.0", tk.END)
        hotkey_config_textbox.insert(tk.END, s)

    def update_print_message_textbox():
        """更新消息显示文本框的内容"""
        if not qpq.empty():
            text = str(qpq.get())
            print_message_textbox.config(state=tk.NORMAL)
            print_message_textbox.insert(tk.END, text + "\n")

            # 获取当前文本内容
            text = print_message_textbox.get("1.0", tk.END)
            # 计算行数和总字符数
            lines = text.split("\n")
            total_chars = len(text)
            total_lines = len(lines)
            # 限制行数和字符数
            max_lines = 20
            max_chars = 30 * max_lines
            # 如果行数或字符数超过限制, 删除前面的内容
            if total_lines > max_lines or total_chars > max_chars:
                # 计算需要保留的内容
                remaining_chars = max_chars
                new_text = ""
                for line in reversed(lines):  # 从后往前遍历
                    if (
                        len(line) + len(new_text) + 1 <= remaining_chars
                    ):  # +1 是为了换行符
                        new_text = line + "\n" + new_text
                    else:
                        break
                # 删除超出限制的内容
                print_message_textbox.delete("1.0", tk.END)
                print_message_textbox.insert(tk.INSERT, new_text.strip())

            print_message_textbox.see(tk.END)
            print_message_textbox.config(state=tk.DISABLED)
        root.after(500, update_print_message_textbox)  # 每隔 0.5 秒检查一次队列

    # region Tkinter 界面
    root = tk.Tk()
    root.title("超级K - 键盘刷视频")
    # root.geometry("419x206") # 419, 206
    root.resizable(False, False)
    root.config(bg="#050505")
    set_window_icon_from_base64(root)

    # 上半部分, 下半部分和页脚
    frame_top = tk.Frame(root, bg="#050505")
    frame_top.pack(pady=5, padx=5, fill="both")
    frame_bottom = tk.Frame(root, bg="#050505")
    frame_bottom.pack(pady=5, padx=5, fill="both")
    frame_footer = tk.Frame(root, bg="#050505")
    frame_footer.pack(pady=5, padx=5, fill="both")

    # 上半部分有两个横向排列的frame
    frame_top_1 = tk.Frame(frame_top, bg="#050505")
    frame_top_1.pack(side="left", fill="both")
    frame_top_2 = tk.Frame(frame_top, bg="#050505")
    frame_top_2.pack(side="right", fill="both")

    # frame1标题
    message_label = tk.Label(frame_top_1, text="消息显示:")
    message_label.config(bg="#050505", foreground="#e8e6e3")
    message_label.pack(pady=5)
    # frame1
    frame1 = tk.Frame(frame_top_1, bg="#050505")
    frame1.pack()
    # 输入框, 用于消息显示
    print_message_textbox = scrolledtext.ScrolledText(
        frame1, height=3, width=40, font=("TkDefaultFont", 7), state=tk.DISABLED
    )
    print_message_textbox.config(bg="#383a3b", foreground="#e8e6e3")
    print_message_textbox.pack()

    # frame2标题
    port_label = tk.Label(frame_top_2, text="输入端口号:")
    port_label.config(bg="#050505", foreground="#e8e6e3")
    port_label.pack(pady=5)
    # frame2
    frame2 = tk.Frame(frame_top_2, bg="#050505")
    frame2.pack()
    # 输入框, 用于输入端口号, 并设置默认值
    with shared_ws_port.get_lock():
        current_port = shared_ws_port.value
    port_entry = DebouncedNumericEntry(
        frame2,
        justify="center",
        width=9,
        textvariable=tk.StringVar(value=str(current_port)),
    )
    port_entry.config(bg="#383a3b", fg="#e8e6e3")
    port_entry.pack(side="left", fill="y")
    # 按钮, 用于重启 WebSocket 服务器
    restart_websocket_button = tk.Button(
        frame2, text="设置端口号", width=14, command=restart_websocket
    )
    restart_websocket_button.config(bg="#217fab", fg="#e8e6e3")
    restart_websocket_button.pack(side="left")

    # 下半部分只有这一个frame
    # frame3标题
    hotkey_config_label = tk.Label(frame_bottom, text="快捷键设置:")
    hotkey_config_label.config(bg="#050505", foreground="#e8e6e3")
    hotkey_config_label.pack(pady=5)
    # frame3
    frame3 = tk.Frame(frame_bottom, bg="#050505")
    frame3.pack()
    # 输入框, 用于输入快捷键
    hotkey_config_textbox = scrolledtext.ScrolledText(frame3, height=5)
    hotkey_config_textbox.config(bg="#383a3b", foreground="#e8e6e3", width=40)
    hotkey_config_textbox.insert(tk.END, dict_to_hotkey_str(user_hotkey))
    check_hotkey_config_text_limit()  # insert之后主动截断一次过长的文本
    hotkey_config_textbox.bind(
        "<KeyRelease>", check_hotkey_config_text_limit
    )  # 绑定事件（覆盖所有修改方式）
    hotkey_config_textbox.bind("<<Paste>>", check_hotkey_config_text_limit)
    hotkey_config_textbox.bind("<<Cut>>", check_hotkey_config_text_limit)
    hotkey_config_textbox.pack(side="left")
    # frame3_btn
    frame3_btn = tk.Frame(frame3, bg="#050505")
    frame3_btn.pack(side="left", fill="both")
    # 按钮, 随机生成快捷键字符串
    generate_random_hotkeys_button = tk.Button(
        frame3_btn,
        text="随机生成快捷键",
        width=14,
        command=set_textbox_with_random_hotkeys,
    )
    generate_random_hotkeys_button.config(bg="#217fab", fg="#e8e6e3")
    generate_random_hotkeys_button.pack(side="top")
    # 按钮, 用于重启 pynput 监听
    restart_keyboard_listener_button = tk.Button(
        frame3_btn, text="设置快捷键", width=14, command=restart_keyboard_listener
    )
    restart_keyboard_listener_button.config(bg="#217fab", fg="#e8e6e3")
    restart_keyboard_listener_button.pack(side="top")

    # 页脚部分
    frame_4 = tk.Frame(frame_footer, bg="#050505")
    frame_4.pack()
    tampermonkey_script_button = tk.Button(frame_4, text="获取浏览器脚本",command=lambda: show_script_window(root))
    tampermonkey_script_button.grid(row=0, column=1, padx=10)
    project_homepage = tk.Button(
        frame_4, text="项目主页", command=open_project_homepage
    )
    project_homepage.grid(row=0, column=2, padx=10)
    # endregion

    # region Tkinter 事件
    # 捕获窗口关闭事件
    root.protocol("WM_DELETE_WINDOW", on_close)

    watch_stop_event()

    update_print_message_textbox()

    try:
        root.mainloop()
    except KeyboardInterrupt:
        pass

    print("tkinter进程已结束")
    # endregion


# 第二个进程：运行 WebSocket 服务器
async def echo_client_messages(ws: WSSConn, qpq: Queue):
    async for message in ws:
        current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        r_info = "{} - 收到来自客户端 {} 的消息: \033[1;33m{}\033[0m".format(
            current_time, get_client_address_str(ws), message
        )
        queue_print(qpq, r_info)

        # 回显消息给客户端
        fmt_message = "服务端收到了你的消息：" + message
        await ws.send(fmt_message)

        s_info = "{} - 向客户端 {} 发送回显消息: \033[1;36m{}\033[0m".format(
            current_time, get_client_address_str(ws), fmt_message
        )
        queue_print(qpq, s_info)


async def register(websocket: WSSConn, clients_ctx: ContextVar, qpq: Queue):
    clients: set = clients_ctx.get()
    clients.add(websocket)
    echo_task = asyncio.create_task(echo_client_messages(websocket, qpq))

    # queue_print(qpq, connections2str(connections))
    queue_print(qpq, get_connection_status_message(websocket))

    try:
        await websocket.wait_closed()
    finally:
        clients.remove(websocket)
        echo_task.cancel()

        # queue_print(qpq, connections2str(connections))
        queue_print(qpq, get_connection_status_message(websocket, is_new=False))


async def show_message(
    stop_process_event: MPEventType,
    restart_ws_event: MPEventType,
    msg_queue: Queue,
    clients_ctx: ContextVar,
):
    while not stop_process_event.is_set():
        try:
            # 使用 asyncio.to_thread 包装同步的 queue.get(), 并设置超时
            message = await asyncio.to_thread(msg_queue.get, timeout=0.5)
        except QueueEmpty:
            # 队列为空(超时引发 QueueEmpty), 继续循环, 下一轮再检查 stop_process_event
            message = None  # 标记为队列为空

        if message is not None:
            # 广播消息
            connections = clients_ctx.get()
            broadcast(connections, message)

        # 监听 restart_ws_event 是否触发
        if restart_ws_event.is_set():
            restart_ws_event.clear()
            break


async def start_websocket_server(
    stop_process_event: MPEventType,
    restart_ws_event: MPEventType,
    msg_queue: Queue,
    clients_ctx: ContextVar,
    shared_ws_port: MPValueType,
    qpq: Queue,
    config_save_queue: Queue,
):
    while not stop_process_event.is_set():
        with shared_ws_port.get_lock():
            current_port = shared_ws_port.value
        queue_print(qpq, f"正在启动 WebSocket 服务, 监听端口：{current_port}")
        try:
            async with serve(
                lambda websocket: register(websocket, clients_ctx, qpq),
                "localhost",
                current_port,
            ):
                text = f"WebSocket 服务启动成功, 监听端口：{current_port}"
                config_save_queue.put({"port": current_port})

                queue_print(qpq, text)
                await show_message(
                    stop_process_event, restart_ws_event, msg_queue, clients_ctx
                )
        except OSError as e:
            queue_print(qpq, f"WebSocket 服务启动失败。错误原因：{e}")
            for _ in range(10):  # 每次睡眠 0.1 秒, 共 1 秒
                time.sleep(0.1)
                if stop_process_event.is_set():
                    break


def run_websocket_server(
    stop_process_event: MPEventType,
    restart_ws_event: MPEventType,
    msg_queue: Queue,
    shared_ws_port: MPValueType,
    qpq: Queue,
    config_save_queue: Queue,
):
    clients_ctx = ContextVar("clients_ctx", default=set())
    try:
        asyncio.run(
            start_websocket_server(
                stop_process_event,
                restart_ws_event,
                msg_queue,
                clients_ctx,
                shared_ws_port,
                qpq,
                config_save_queue,
            )
        )
    except KeyboardInterrupt:
        pass


# 第三个进程：使用 pynput 监听键盘按键
def run_pynput_listener(
    stop_process_event: MPEventType,
    server_to_client_msg_queue: Queue,
    restart_pl_event: MPEventType,
    hotkey_config_queue: Queue,
    qpq: Queue,
    default_hotkey: Dict[str, str],
    config_save_queue: Queue,
):
    """
    使用 pynput 监听热键, 并将消息传递给 WebSocket 进程。
    """

    def on_hotkey_activated(message: str):
        """
        热键激活时的回调函数
        """
        queue_print(qpq, "发送:" + message)
        server_to_client_msg_queue.put(message)  # 将消息传递给 WebSocket 进程

    def generate_default_shortcuts(used_shortcuts: set = None):
        """
        生成默认快捷键, 但会尽量排除已经使用的快捷键。
        即使快捷键都用过, 也不删光 shortcuts, 会保留一个。

        :param used_shortcuts: 已经使用的快捷键集合, 默认为 None。如果为 None, 则初始化为空集合。
        :return: 生成器, 依次返回可用的默认快捷键。
        """
        if used_shortcuts is None:
            used_shortcuts = set()

        default_shortcuts = ["<ctrl>+<f1>", "<ctrl>+<f2>", "<ctrl>+<f3>"]

        # 遍历 used_shortcuts, 删除已经使用的快捷键
        for used_shortcut in used_shortcuts:
            if used_shortcut in default_shortcuts:
                if len(default_shortcuts) == 1:  # 留一个
                    break
                default_shortcuts.remove(used_shortcut)

        # 生成默认快捷键
        while True:
            for shortcut in default_shortcuts:
                yield shortcut

    def create_hotkeys(hotkey_config: Dict[str, str]) -> List[keyboard.HotKey]:
        """
        根据热键配置生成热键对象并返回。
        如果热键配置无效(抛出 ValueError), 则使用默认快捷键。

        :param hotkey_config: 热键配置字典, 格式为 {"组合键": 消息}
        :return: 生成的热键对象列表
        """
        hotkeys = []
        default_shortcuts_generator = generate_default_shortcuts(
            set(hotkey_config.keys())
        )

        for hotkey_combination, message in hotkey_config.items():
            try:
                hotkey = keyboard.HotKey(
                    keyboard.HotKey.parse(hotkey_combination),
                    lambda msg=message: on_hotkey_activated(msg),
                )
                hotkeys.append(hotkey)
            except ValueError as e:
                # 如果解析失败, 使用默认快捷键
                queue_print(
                    qpq,
                    f"'{hotkey_combination}'是无效的热键组合, 无法识别其中的 {e}",
                )
                default_shortcut = next(default_shortcuts_generator)
                hotkey = keyboard.HotKey(
                    keyboard.HotKey.parse(default_shortcut),
                    lambda msg=message: on_hotkey_activated(msg),
                )
                hotkeys.append(hotkey)
                queue_print(qpq, f"已使用默认快捷键 {default_shortcut} 进行替换")

        return hotkeys

    # region pynput循环
    # 初始化热键
    hotkey_config = default_hotkey
    hotkeys = create_hotkeys(hotkey_config)

    while not stop_process_event.is_set():
        # 检查是否有新的快捷键配置需要加载
        if not hotkey_config_queue.empty():
            new_hotkey_config_str = hotkey_config_queue.get()
            new_hotkey_config = hotkey_str_to_dict(new_hotkey_config_str)
            queue_print(qpq, "加载新的快捷键配置:")
            queue_print(qpq, new_hotkey_config)
            hotkey_config = new_hotkey_config
            hotkeys = create_hotkeys(hotkey_config)
            queue_print(qpq, "快捷键已更新")
            # 将新配置放入 config_save_queue
            config_save_queue.put({"hotkeys": new_hotkey_config})
        else:
            hotkey_config = default_hotkey
            hotkeys = create_hotkeys(hotkey_config)

        # 定义按键按下和释放的处理函数
        def on_press(key: keyboard.Key | keyboard.KeyCode):
            if stop_process_event.is_set():
                return False  # 停止监听
            for hotkey in hotkeys:
                if hotkey.press(listener.canonical(key)):
                    hotkey._notify_listeners(key)

        def on_release(key: keyboard.Key | keyboard.KeyCode):
            for hotkey in hotkeys:
                if hotkey.release(listener.canonical(key)):
                    hotkey._notify_listeners(key)

        # 创建键盘监听器
        listener = keyboard.Listener(on_press=on_press, on_release=on_release)
        queue_print(qpq, "pynput已启动")
        listener.start()  # 启动监听器线程
        while not stop_process_event.is_set() and not restart_pl_event.is_set():
            time.sleep(0.3)  # 等待 stop_event 或 restart_pl_event 被设置

        # 如果 restart_pl_event 被设置, 则重启监听器
        if restart_pl_event.is_set():
            queue_print(qpq, "重启pynput")
            restart_pl_event.clear()  # 清除 restart_pl_event 的状态
            listener.stop()  # 停止当前监听器
            listener.join()  # 等待监听器线程结束
        else:
            break  # 如果 stop_event 被设置, 则退出循环

    queue_print(qpq, "pynput进程已结束")
    # endregion


# 保存
def save_final_config(config_manager, config_save_queue, user_port, user_hotkeys):
    """
    从队列中获取最新配置并保存到配置文件。

    :param config_manager: 配置管理器对象
    :param config_save_queue: 保存配置的队列
    :param user_port: 用户的默认端口号
    :param user_hotkeys: 用户的默认快捷键配置
    """
    # 初始化保存的配置
    saved_configs = {"port": user_port, "hotkeys": user_hotkeys}

    # 处理队列中的所有配置更新
    while not config_save_queue.empty():
        config = config_save_queue.get()
        if "port" in config:
            saved_configs["port"] = config["port"]
        if "hotkeys" in config:
            saved_configs["hotkeys"] = config["hotkeys"]

    # 使用 ConfigManager 保存最新的配置
    config_manager.save_user_config(
        port=saved_configs["port"], hotkeys=saved_configs["hotkeys"]
    )

    # 打印保存的配置
    print("配置已保存")
    print(saved_configs)


def main():
    config_manager = ConfigManager()
    USER_HOTKEYS = config_manager.get_user_hotkeys()

    stop_process_event = MPEvent()  # 控制子进程的停止
    restart_ws_event = MPEvent()  # 通知子进程重启 WebSocket 服务
    restart_pl_event = MPEvent()  # 通知子进程重启 pynput 监听
    server_to_client_msg_queue = Queue()  # 服务端传递消息到客户端的队列
    shared_ws_port = Value("i", config_manager.get_user_port())
    hotkey_config_queue = Queue()
    qpq = Queue()  # queue_print()需要使用这个队列
    config_save_queue = Queue()

    # 启动 Tkinter 子进程
    tkinter_process = Process(
        target=run_tkinter_interface,
        args=(
            stop_process_event,
            restart_ws_event,
            restart_pl_event,
            shared_ws_port,
            hotkey_config_queue,
            USER_HOTKEYS,
            qpq,
        ),
        name="tkinter",
    )
    tkinter_process.start()

    # 启动 WebSocket 子进程
    websocket_process = Process(
        target=run_websocket_server,
        args=(
            stop_process_event,
            restart_ws_event,
            server_to_client_msg_queue,
            shared_ws_port,
            qpq,
            config_save_queue,
        ),
        name="websocket",
    )
    websocket_process.start()

    # 启动 pynput 子进程
    pynput_process = Process(
        target=run_pynput_listener,
        args=(
            stop_process_event,
            server_to_client_msg_queue,
            restart_pl_event,
            hotkey_config_queue,
            qpq,
            USER_HOTKEYS,
            config_save_queue,
        ),
        name="pynput",
    )
    pynput_process.start()

    # region 主线程发消息
    # message_counter = 0
    # try:
    #     # 主进程定期向队列中放入消息
    #     while True:
    #         time.sleep(1)
    #         message = f"消息编号：{message_counter}"
    #         message_counter += 1
    #         server_to_client_msg_queue.put(message)

    #         if message_counter == 20:
    #             stop_process_event.set()
    #         # 模拟触发 restart_ws_event
    #         if message_counter % 5 == 0:
    #             queue_print(qpq, "触发 WebSocket 服务重启...")
    #             restart_ws_event.set()
    #         # 主线程也要退出
    #         if stop_process_event.is_set():
    #             break
    # except KeyboardInterrupt:
    #     stop_process_event.set()
    #     # 放一条消息进队列(其实这条消息根本没条件发出去)
    #     # 让 asyncio.to_thread(msg_queue.get) 立刻不阻塞(虽然已经设置了超时)
    #     # 避免子进程阻塞时间长无法检查 stop_process_event
    #     server_to_client_msg_queue.put("")
    # endregion

    tkinter_process.join()
    stop_process_event.set()
    websocket_process.join()

    # 调用保存配置的方法
    save_final_config(config_manager, config_save_queue, shared_ws_port, USER_HOTKEYS)


if __name__ == "__main__":
    main()
